﻿using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace HEFramework
{
    /// <summary>
    ///
    /// AB包加载器
    /// 
    /// ----------------------------------------
    /// 作者 hep
    /// 创建时间：2023-12-19 20:13:38
    /// ----------------------------------------
    /// </summary>
    public class AssetBundleLoader : Singleton<AssetBundleLoader>, ISingletonAwake, ISingletonUpdate
    {
        /// <summary>
        /// AB包数据
        /// </summary>
        public class AssetBundleData
        {
            /// <summary>
            /// AB包名称
            /// </summary>
            public string AssetBundleName { get; set; }

            /// <summary>
            /// AB包文件路径
            /// </summary>
            public string AssetBundleFilePath { get; set; }

            /// <summary>
            /// 文件加载请求
            /// </summary>
            public FileCreateRequest FileCreateRequest { get; set; }

            /// <summary>
            /// AB包加载请求
            /// </summary>
            public AssetBundleCreateRequest AssetBundleCreateRequest { get; set; }

            /// <summary>
            /// 回调函数
            /// </summary>
            public List<Action<AssetBundle>> Callbacks { get; set; } = new();

            /// <summary>
            /// AB引用计数
            /// </summary>
            public int ReferenceCount { get; set; }

            /// <summary>
            /// AB资源
            /// </summary>
            public AssetBundle AssetBundle { get; set; }

            /// <summary>
            /// AB包正在加载的依赖包数量
            /// </summary>
            public int DependLoadingCount { get; set; }

            /// <summary>
            /// AB引用的AB信息
            /// </summary>
            public List<AssetBundleData> DependAssetBundles { get; set; } = new();


            /// <summary>
            /// 释放
            /// </summary>
            public void Dispose()
            {
                AssetBundleName = "";
                AssetBundleFilePath = "";


                //取消文件加载
                if (FileCreateRequest != null)
                {
                    FileCreateRequest.Dispose();
                }

                FileCreateRequest = null;


                //强制将AB加载出来
                if (AssetBundleCreateRequest != null)
                {
                    AssetBundle = AssetBundleCreateRequest.assetBundle;
                }

                AssetBundleCreateRequest = null;


                Callbacks.Clear();
                ReferenceCount = 0;

                //卸载AB
                if (AssetBundle != null)
                {
                    AssetBundle.Unload(true);
                }

                AssetBundle = null;

                DependLoadingCount = 0;
                DependAssetBundles.Clear();

                ObjectGenerater.Instance.Recycle(this);
            }
        }


        /// <summary>
        /// 文件请求
        /// </summary>
        public class FileCreateRequest
        {
            public byte[] Bytes { get; set; }
            public HECancellationToken Token { get; set; }

            public void Dispose()
            {
                Bytes = null;
                if (Token != null)
                {
                    Token.Cancel();
                }

                Token = null;
                ObjectGenerater.Instance.Recycle(this);
            }
        }


        /// <summary>
        /// 同时加载的最大数量
        /// </summary>
        private const int MAX_LODING_COUNT = 50;

        /// <summary>
        /// 读取文件的缓存区大小
        /// </summary>
        private const int BUFFER_SIZE = 1024 * 256;


        /// <summary>
        /// AB包的依赖映射组
        /// </summary>
        private Dictionary<string, string[]> assetBundlesDepends = new();

        /// <summary>
        /// AB预备加载组
        /// </summary>
        private Dictionary<string, AssetBundleData> waitLodingAssetBundles = new();


        /// <summary>
        /// AB加载组
        /// </summary>k
        private Dictionary<string, AssetBundleData> nowLodingAssetBundles = new();


        /// <summary>
        /// AB完成组
        /// </summary>
        private Dictionary<string, AssetBundleData> loadAssetBundles = new();


        /// <summary>
        /// AB预备销毁组
        /// </summary>
        private Dictionary<string, AssetBundleData> waitUnloadAssetBundles = new();


        /// <summary>
        /// 创建临时存储变量，用于提升性能
        /// </summary>
        private List<AssetBundleData> tempBundles = new();

        /// <summary>
        /// 创建临时存储变量，用于提升性能
        /// </summary>
        private List<string> tempNames = new();

        /// <summary>
        /// 加密
        /// </summary>
        private Aes aes;

        public void Awake()
        {
            if (AssetBundleSettingScriptableObject.Instance.Encrypt)
            {
                aes = new Aes(AssetBundleSettingScriptableObject.Instance.Key, AssetBundleSettingScriptableObject.Instance.IV);
            }
            else
            {
                aes = null;
            }
        }

        /// <summary> 
        /// 帧更新
        /// </summary>
        public void Update()
        {
            UpdateLoad();
            UpdateWait();
            UpdateUnLoad();
        }

        /// <summary>
        /// 结束
        /// </summary>
        protected override void Destroy()
        {
            foreach (var data in waitLodingAssetBundles.Values)
            {
                data.Dispose();
            }

            foreach (var data in nowLodingAssetBundles.Values)
            {
                data.Dispose();
            }

            foreach (var data in loadAssetBundles.Values)
            {
                data.Dispose();
            }

            waitLodingAssetBundles.Clear();
            nowLodingAssetBundles.Clear();
            loadAssetBundles.Clear();
            waitUnloadAssetBundles.Clear();
            Resources.UnloadUnusedAssets();
        }


        #region 读取卸载包装

        /// <summary>
        /// 读取依赖
        /// </summary>
        public void LoadDependenciesSyn()
        {
            switch (AssetBundleSettingScriptableObject.Instance.Domain)
            {
                case eDomain.Streaming:
                case eDomain.Persistent:
                    //读取总AB包
                    string bundleFileName = AssetBundleSettingScriptableObject.Instance.AssetBundleCentralFileName;
                    if (AssetBundleSettingScriptableObject.Instance.Encrypt)
                    {
                        bundleFileName = MD5Hash.ComputeHashToHex(bundleFileName);
                    }

                    string bundleFilePath = AssetBundleSettingScriptableObject.Instance.GetAssetBundleDataLoadPath() + "/" + bundleFileName;
                    byte[] datas = File.ReadBytesSyn(bundleFilePath, aes);
                    AssetBundle manifestBundle = AssetBundle.LoadFromMemory(datas);
                    AssetBundleManifest assetBundleManifest = manifestBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
                    string[] assetBundleNames = assetBundleManifest.GetAllAssetBundles();
                    foreach (string assetBundleName in assetBundleNames)
                    {
                        string abName = assetBundleName;
                        string[] dependencies = assetBundleManifest.GetAllDependencies(abName);

                        if (AssetBundleSettingScriptableObject.Instance.Encrypt)
                        {
                            abName = MD5Hash.ComputeHashToHex(abName);
                            for (int i = 0; i < dependencies.Length; i++)
                            {
                                dependencies[i] = MD5Hash.ComputeHashToHex(dependencies[i]);
                            }
                        }

                        assetBundlesDepends.Add(abName, dependencies);
                    }

                    manifestBundle.Unload(true);
                    break;
            }
        }

        /// <summary>
        /// 读取某个AB包内某个T类型数据（同步）
        /// </summary>
        /// <param name="_assetBundleFilePath">AB包文件完整读取路径</param>
        /// <param name="_assetBundleName">AB包全名</param>
        /// <param name="_assetName">资源名称</param>
        /// <typeparam name="T"></typeparam>
        public T LoadAssetSyn<T>(string _assetBundleFilePath, string _assetBundleName, string _assetName) where T : UnityEngine.Object
        {
            if (_assetName == null)
            {
                //不写具体读取内容 只读取包
                LoadAssetBundleSyn(_assetBundleFilePath, _assetBundleName);
                return null;
            }

            AssetBundle assetBundle = LoadAssetBundleSyn(_assetBundleFilePath, _assetBundleName).AssetBundle;
            T a = assetBundle.LoadAsset<T>(_assetName);
            return a;
        }

        /// <summary>
        /// 读取某个AB包内某个T类型数据（异步）
        /// </summary>
        /// <param name="_assetBundleFilePath">AB包文件完整读取路径</param>
        /// <param name="_assetBundleName">AB包全名</param>
        /// <param name="_assetName">资源名称</param>
        /// <param name="_callback">资源回调函数</param>
        public void LoadAssetAsync<T>(string _assetBundleFilePath, string _assetBundleName, string _assetName, Action<T> _callback) where T : UnityEngine.Object
        {
            LoadAssetBundleAsync(_assetBundleFilePath, _assetBundleName, _ =>
            {
                if (_ == null)
                {
                    //未加载出数据 返回空值
                    _callback(null);
                }
                else
                {
                    if (_assetName == null)
                    {
                        //不写具体读取内容 只读取包
                        _callback(null);
                        return;
                    }

                    _callback(_.LoadAsset<T>(_assetName));
                }
            });
        }


        /// <summary>
        /// 卸载AB包（异步）
        /// </summary>
        /// <param name="_assetBundleName"></param>
        public void UnLoadAssetAsync(string _assetBundleName)
        {
            UnloadAssetBundleAsync(_assetBundleName);
        }

        #endregion


        #region 读取卸载核心

        /// <summary>
        /// 加载AB包（同步）
        /// </summary>
        /// <param name="_assetBundleFilePath">Ab包文件完整路径</param>
        /// <param name="_assetBundleName">Ab包名字</param>
        /// <returns></returns>
        private AssetBundleData LoadAssetBundleSyn(string _assetBundleFilePath, string _assetBundleName)
        {
            AssetBundleData data = null;

            //已经加载
            if (loadAssetBundles.TryGetValue(_assetBundleName, out data))
            {
                data.ReferenceCount++;
                foreach (var value in data.DependAssetBundles)
                {
                    //递归依赖项
                    LoadAssetBundleSyn(value.AssetBundleFilePath, value.AssetBundleName);
                }

                return data;
            }

            //在加载中,异步改同步
            if (nowLodingAssetBundles.TryGetValue(_assetBundleName, out data))
            {
                data.ReferenceCount++;
                foreach (var value in data.DependAssetBundles)
                {
                    //递归依赖项
                    LoadAssetBundleSyn(value.AssetBundleFilePath, value.AssetBundleName);
                }

                //强制完成，回调
                DoLoadCallback(data);
                return data;
            }

            //在准备加载中
            if (waitLodingAssetBundles.TryGetValue(_assetBundleName, out data))
            {
                data.ReferenceCount++;
                foreach (var value in data.DependAssetBundles)
                {
                    //递归依赖项
                    LoadAssetBundleSyn(value.AssetBundleFilePath, value.AssetBundleName);
                }

                data.AssetBundle = AssetBundle.LoadFromMemory(File.ReadBytesSyn(data.AssetBundleFilePath, aes));
                waitLodingAssetBundles.Remove(data.AssetBundleName);
                loadAssetBundles.Add(data.AssetBundleName, data);
                //强制完成，回调
                DoLoadCallback(data);
                return data;
            }


            //创建一个新的AB加载
            data = ObjectGenerater.Instance.Fetch<AssetBundleData>();
            data.AssetBundleName = _assetBundleName;
            data.AssetBundleFilePath = _assetBundleFilePath;
            data.ReferenceCount = 1;
            data.AssetBundle = AssetBundle.LoadFromMemory(File.ReadBytesSyn(data.AssetBundleFilePath, aes));


            //加载依赖项
            string[] dependsDatas = null;
            assetBundlesDepends.TryGetValue(_assetBundleName, out dependsDatas);
            if (dependsDatas != null && dependsDatas.Length > 0)
            {
                data.DependLoadingCount = 0;
                foreach (var dpAssetName in dependsDatas)
                {
                    string abFilePath = AssetBundleSettingScriptableObject.Instance.GetAssetBundleDataLoadPath() + "/" + dpAssetName;
                    var dpInfo = LoadAssetBundleSyn(abFilePath, dpAssetName);
                    data.DependAssetBundles.Add(dpInfo);
                }
            }

            loadAssetBundles.Add(data.AssetBundleName, data);
            return data;
        }


        /// <summary>
        /// 加载AB包（异步）
        /// </summary>
        /// <param name="_assetBundleFilePath">AB包文件完整路径</param>
        /// <param name="_assetBundleName">AB包名字</param>
        /// <param name="_callback">AB包接收回调</param>
        /// <returns></returns>
        private AssetBundleData LoadAssetBundleAsync(string _assetBundleFilePath, string _assetBundleName, Action<AssetBundle> _callback)
        {
            AssetBundleData data = null;

            //已经加载
            if (loadAssetBundles.TryGetValue(_assetBundleName, out data))
            {
                DoDependsReference(data);
                _callback(data.AssetBundle);
                return data;
            }

            //在加载中
            if (nowLodingAssetBundles.TryGetValue(_assetBundleName, out data))
            {
                DoDependsReference(data);
                data.Callbacks.Add(_callback);
                return data;
            }

            //在准备加载中
            if (waitLodingAssetBundles.TryGetValue(_assetBundleName, out data))
            {
                DoDependsReference(data);
                data.Callbacks.Add(_callback);
                return data;
            }


            //创建一个加载
            data = ObjectGenerater.Instance.Fetch<AssetBundleData>();
            data.AssetBundleName = _assetBundleName;
            data.AssetBundleFilePath = _assetBundleFilePath;
            data.ReferenceCount = 1;
            data.Callbacks.Add(_callback);


            //加载依赖项
            string[] dependDatas = null;
            assetBundlesDepends.TryGetValue(_assetBundleName, out dependDatas);
            if (dependDatas != null && dependDatas.Length > 0)
            {
                data.DependLoadingCount = dependDatas.Length;
                foreach (var dpAssetName in dependDatas)
                {
                    string abFilePath = AssetBundleSettingScriptableObject.Instance.GetAssetBundleDataLoadPath() + "/" + dpAssetName;
                    var bundleInfo = LoadAssetBundleAsync(abFilePath, dpAssetName, (_ab) =>
                        {
                            if (data.DependLoadingCount <= 0)
                            {
                                Log.Error($"[Depend] error ! assetName:{dpAssetName}");
                                return;
                            }

                            data.DependLoadingCount--;
                        }
                    );
                    data.DependAssetBundles.Add(bundleInfo);
                }
            }


            waitLodingAssetBundles.Add(_assetBundleName, data);
            return data;
        }


        /// <summary>
        /// 异步卸载AB包
        /// </summary>
        /// <param name="_assetBundleName">AB包名称</param>
        private void UnloadAssetBundleAsync(string _assetBundleName)
        {
            AssetBundleData data = null;

            if (loadAssetBundles.TryGetValue(_assetBundleName, out data))
            {
            }
            else if (nowLodingAssetBundles.TryGetValue(_assetBundleName, out data))
            {
            }
            else if (waitLodingAssetBundles.TryGetValue(_assetBundleName, out data))
            {
            }

            if (data == null)
            {
                Log.Error($"[UnloadAssetBundle] error ! assetName:{_assetBundleName} ");
                return;
            }

            if (data.ReferenceCount == 0)
            {
                Log.Error($"[UnloadAssetBundle] referenceCount error ! assetName:{_assetBundleName} ");
                return;
            }

            data.ReferenceCount--;

            foreach (var value in data.DependAssetBundles)
            {
                UnloadAssetBundleAsync(value.AssetBundleName);
            }

            if (data.ReferenceCount == 0)
            {
                waitUnloadAssetBundles.Add(data.AssetBundleName, data);
            }
        }

        #endregion


        #region 内部接口

        /// <summary>
        /// 等待加载的AB包处理
        /// </summary>
        private void UpdateWait()
        {
            if (waitLodingAssetBundles.Count <= 0)
            {
                return;
            }

            //正在加载的数量判断
            if (nowLodingAssetBundles.Count >= MAX_LODING_COUNT)
            {
                return;
            }

            tempBundles.Clear();

            foreach (var value in waitLodingAssetBundles.Values)
            {
                tempBundles.Add(value);

                //开始读取AB包
                DoLoadFile(value).Coroutine();

                //转移队列
                nowLodingAssetBundles.Add(value.AssetBundleName, value);

                if (nowLodingAssetBundles.Count >= MAX_LODING_COUNT)
                {
                    break;
                }
            }

            foreach (var value in tempBundles)
            {
                waitLodingAssetBundles.Remove(value.AssetBundleName);
            }
        }

        /// <summary>
        /// 加载中的AB包处理
        /// </summary>
        private void UpdateLoad()
        {
            if (nowLodingAssetBundles.Count <= 0)
            {
                return;
            }

            tempBundles.Clear();

            //检测是否有加载完成的AB包
            foreach (var value in nowLodingAssetBundles.Values)
            {
                //文件已读取完成，但是AB包还没解析
                if (value.AssetBundleCreateRequest == null && value.FileCreateRequest.Bytes != null)
                {
                    DoLoadAssetBundle(value);
                    continue;
                }

                //只有其依赖的AB包全部加载出来才算该AB包加载完成
                //AB包递归完成加载
                //无法解决A->B  B->A的问题，因此AB包之间的依赖需保持树形结构
                if (value.DependLoadingCount == 0 && value.AssetBundleCreateRequest != null && value.AssetBundleCreateRequest.isDone)
                {
                    tempBundles.Add(value);
                }
            }

            foreach (var value in tempBundles)
            {
                //加载完进行回调
                DoLoadCallback(value);
            }
        }

        /// <summary>
        /// 遍历引用计数为0的AB是否销毁
        /// </summary>
        private void UpdateUnLoad()
        {
            if (waitUnloadAssetBundles.Count <= 0)
            {
                return;
            }

            tempNames.Clear();

            foreach (var value in waitUnloadAssetBundles.Values)
            {
                //引用计数为0并且已经加载完成的AB包
                if (value.ReferenceCount == 0 && value.AssetBundle != null)
                {
                    //完成队列中移除
                    loadAssetBundles.Remove(value.AssetBundleName);
                    tempNames.Add(value.AssetBundleName);
                    DoUnLoad(value);
                }

                //等待卸载的途中又被引用，则从卸载队列中移除
                if (value.ReferenceCount > 0)
                {
                    tempNames.Add(value.AssetBundleName);
                }
            }

            foreach (var value in tempNames)
            {
                waitUnloadAssetBundles.Remove(value);
            }
        }

        /// <summary>
        /// 读取文件字节
        /// </summary>
        /// <param name="_data"></param>
        private async HETask DoLoadFile(AssetBundleData _data)
        {
            FileCreateRequest request = ObjectGenerater.Instance.Fetch<FileCreateRequest>();
            request.Token = new HECancellationToken();
            _data.FileCreateRequest = request;
            byte[] bytes = await File.ReadBytesAsync(_data.AssetBundleFilePath, aes, request.Token, BUFFER_SIZE);
            if (_data.FileCreateRequest == null)
            {
                return;
            }

            _data.FileCreateRequest.Bytes = bytes;
        }

        /// <summary>
        /// 读取AB包
        /// </summary>
        /// <param name="_data"></param>	
        private void DoLoadAssetBundle(AssetBundleData _data)
        {
            byte[] bytes = _data.FileCreateRequest.Bytes;
            _data.AssetBundleCreateRequest = AssetBundle.LoadFromMemoryAsync(bytes);
            if (_data.AssetBundleCreateRequest == null)
            {
                Log.Error($"[DoLoad] error ! assetName: {_data.AssetBundleName}");
            }
        }

        /// <summary>
        /// 执行AB包回调
        /// </summary>
        private void DoLoadCallback(AssetBundleData _data)
        {
            FileCreateRequest fileCreateRequest = _data.FileCreateRequest;
            //该包异步读取相关（针对nowLoding的资源）
            if (fileCreateRequest != null)
            {
                //检验文件是否读取完成
                if (fileCreateRequest.Bytes == null)
                {
                    //停止文件异步读取
                    fileCreateRequest.Token.Cancel();
                    //改为同步读取方式
                    byte[] bytes = File.ReadBytesSyn(_data.AssetBundleFilePath, aes);
                    _data.AssetBundleCreateRequest = AssetBundle.LoadFromMemoryAsync(bytes);
                }

                fileCreateRequest.Dispose();
                _data.FileCreateRequest = null;


                //检验AB文件是否读取完成
                if (_data.AssetBundleCreateRequest != null)
                {
                    //如果没加载完，会异步转同步
                    _data.AssetBundle = _data.AssetBundleCreateRequest.assetBundle;
                    nowLodingAssetBundles.Remove(_data.AssetBundleName);
                    loadAssetBundles.Add(_data.AssetBundleName, _data);
                }

                _data.AssetBundleCreateRequest = null;
            }

            //运行回调
            foreach (var value in _data.Callbacks)
            {
                value(_data.AssetBundle);
            }

            _data.Callbacks.Clear();
        }

        /// <summary>
        /// 卸载AB包
        /// </summary>
        /// <param name="_data"></param>
        private void DoUnLoad(AssetBundleData _data)
        {
            //这里用true，卸载Asset内存，实现指定卸载
            if (_data.AssetBundle == null)
            {
                Log.Error($"[DoUnload] error ! assetName:{_data.AssetBundleName}");
                return;
            }

            _data.Dispose();
        }

        /// <summary>
        /// AB包引用计数增加
        /// </summary>
        /// <param name="_data"></param>
        private void DoDependsReference(AssetBundleData _data)
        {
            _data.ReferenceCount++;

            if (_data.DependAssetBundles.Count == 0)
            {
                return;
            }

            foreach (var value in _data.DependAssetBundles)
            {
                DoDependsReference(value);
            }
        }

        #endregion
    }
}